/**
 * KopsoxFramework
 * Copyright (C) 2010 Free Software Foundation, Inc. <http://fsf.org/>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kopsox.util.xml.parser.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import org.kopsox.data.ValueDescriptor;
import org.kopsox.data.impl.ValueDescriptorImpl;
import org.kopsox.general.priority.PriorityEnum;
import org.kopsox.general.range.Range;
import org.kopsox.general.range.impl.BigDecimalRange;
import org.kopsox.general.range.impl.DateRange;
import org.kopsox.general.range.impl.DoubleRange;
import org.kopsox.general.range.impl.IntegerRange;
import org.kopsox.general.type.ValueTypeEnum;
import org.kopsox.util.xml.data.XMLValueDescriptor;
import org.kopsox.util.xml.data.impl.NamePriorityDescriptor;
import org.kopsox.util.xml.data.impl.XMLValueDescriptorImpl;
import org.kopsox.util.xml.parser.XMLEnum;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * @author Konrad Renner - 23.07.2010
 *
 */
public class ValueXMLParser extends AbstractKopsoxXMLParser {
	
	protected static final String DEFAULT_TYPE = "java.lang.String";
	
	private final List<XMLValueDescriptor> descriptors;
	private int currentIndex;

	public ValueXMLParser(String file) {
		super(file);
		this.descriptors = new ArrayList<XMLValueDescriptor>();
		this.currentIndex = -1;
	}
	
	public List<XMLValueDescriptor> getValueDescriptors() {
		return getValueDescriptors(null);
	}
	
	/**
	 * TODO Issue 28
	 * 
	 * @param namespace  
	 * @return List<XMLValueDescriptor>
	 */
	public List<XMLValueDescriptor> getValueDescriptors(String namespace) {
		return this.descriptors;
	}

	/* (non-Javadoc)
	 * @see org.kopsox.util.xml.parser.impl.AbstractKopsoxXMLParser#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	@Override
	public void startElement(String namespaceURI, String localName,String qName, Attributes atts) throws SAXException {
		
		if(XMLEnum.VALUE.toString().equals(qName)) {
	    	String name = atts.getValue(0);
	    	XMLValueDescriptorImpl impl = new XMLValueDescriptorImpl();
	    	
	    	ValueDescriptorMutableImpl tempDescriptor = new ValueDescriptorMutableImpl();
	    	tempDescriptor.setName(name);
	    	
	    	impl.setDescriptor(tempDescriptor);
	    	if(atts.getLength() > 0) {
	    		impl.setTypeFromValue(atts.getValue(1));
	    	}else {
	    		impl.setTypeFromValue(DEFAULT_TYPE);
	    	}
	    	
	    	this.descriptors.add(impl);
	    	this.currentIndex++;
	    }else if(XMLEnum.ISNULLABLE.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	ValueDescriptorMutableImpl mutable = (ValueDescriptorMutableImpl)impl.getDescriptor();
	    	mutable.setNullable(Boolean.parseBoolean(atts.getValue(0)));
	    }else if(XMLEnum.ISCHANGEABLE.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	ValueDescriptorMutableImpl mutable = (ValueDescriptorMutableImpl)impl.getDescriptor();
	    	mutable.setChangeable(Boolean.parseBoolean(atts.getValue(0)));
	    }else if(XMLEnum.ISAUTOVALIDATING.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	ValueDescriptorMutableImpl mutable = (ValueDescriptorMutableImpl)impl.getDescriptor();
	    	mutable.setAutoValidationEnabled(Boolean.parseBoolean(atts.getValue(0)));
	    }else if(XMLEnum.ISAUTOERRORDELEGATION.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	ValueDescriptorMutableImpl mutable = (ValueDescriptorMutableImpl)impl.getDescriptor();
	    	mutable.setAutomaticErrorDelegation(Boolean.parseBoolean(atts.getValue(0)));
	    }else if(XMLEnum.ISHIDDEN.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	ValueDescriptorMutableImpl mutable = (ValueDescriptorMutableImpl)impl.getDescriptor();
	    	mutable.setHidden(Boolean.parseBoolean(atts.getValue(0)));
	    }else if(XMLEnum.DATA.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	impl.setInitialValue(atts.getValue(0));
	    }else if(XMLEnum.RANGEOFVALUE.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	ValueDescriptorMutableImpl mutable = (ValueDescriptorMutableImpl)impl.getDescriptor();
	    	
	    	if(atts.getLength() > 1) {
	    		mutable.setRangeOfValue(createRange(atts.getValue(0), atts.getValue(1), impl.getTypeFromValue()));
	    	}else if(XMLEnum.LOWERLIMIT.toString().equalsIgnoreCase(atts.getQName(0))) {
	    		mutable.setRangeOfValue(createRange(atts.getValue(0), null, impl.getTypeFromValue()));
	    	}else{
	    		mutable.setRangeOfValue(createRange(null, atts.getValue(0), impl.getTypeFromValue()));
	    	}
	    }else if(XMLEnum.RANGEOFDIGITSFROMVALUE.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	//Check if both upper und lowerLimit are avaible, if not, check what type of limit is avaiable
	    	IntegerRange range;
	    	if(atts.getLength() > 1) {
	    		range = new IntegerRange((Integer)ValueTypeEnum.INTEGER.convert(atts.getValue(0)),(Integer)ValueTypeEnum.INTEGER.convert(atts.getValue(1)));
	    	}else if(XMLEnum.LOWERLIMIT.toString().equalsIgnoreCase(atts.getQName(0))) {
	    		range = new IntegerRange((Integer)ValueTypeEnum.INTEGER.convert(atts.getValue(0)),null);
	    	}else{
	    		range = new IntegerRange(null,(Integer)ValueTypeEnum.INTEGER.convert(atts.getValue(0)));
	    	}
	    	
	    	ValueDescriptorMutableImpl mutable = (ValueDescriptorMutableImpl)impl.getDescriptor();
	    	mutable.setRangeOfDigits(range);
	    }else if(XMLEnum.VALIDATORS.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	impl.setValidators(new HashSet<NamePriorityDescriptor>());
	    }else if(XMLEnum.VALIDATOR.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	String priority;
	    	if(atts.getLength() > 1) {
	    		priority = atts.getValue(1);
	    	}else {
	    		priority = Integer.toString(PriorityEnum.MEDIUM.getPriority().getPriorityValue());
	    	}
	    	
	    	
	    	NamePriorityDescriptor desc = new NamePriorityDescriptor(atts.getValue(0),priority);
	    	
	    	impl.getValidators().add(desc);
	    }else if(XMLEnum.OBSERVERS.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	impl.setObservers(new HashSet<NamePriorityDescriptor>());
	    }else if(XMLEnum.OBSERVER.toString().equals(qName)) {
	    	XMLValueDescriptorImpl impl = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
	    	
	    	String priority;
	    	if(atts.getLength() > 1) {
	    		priority = atts.getValue(1);
	    	}else {
	    		priority = Integer.toString(PriorityEnum.MEDIUM.getPriority().getPriorityValue());
	    	}
	    	
	    	
	    	NamePriorityDescriptor desc = new NamePriorityDescriptor(atts.getValue(0),priority);
	    	
	    	impl.getObservers().add(desc);
	    }
		
	}
	

	/* (non-Javadoc)
	 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public void endElement(String namespaceURI, String localName,String qName) throws SAXException {
		//If the Value-Element is finished, create an correct ValueDescriptor
		if(XMLEnum.VALUE.toString().equals(qName)) {
			XMLValueDescriptorImpl desc = (XMLValueDescriptorImpl)this.descriptors.get(this.currentIndex);
			
			ValueDescriptorMutableImpl mutable = (ValueDescriptorMutableImpl)desc.getDescriptor();
			
			ValueDescriptorImpl correctImpl = new ValueDescriptorImpl.ValueDescriptorBuilder(mutable.getName()).namespace(mutable.getNamespace()).isAutoValidationEnabled(mutable.isAutoValidationEnabled())
					.isAutoErrorDelegationEnabled(mutable.isAutomaticErrorDelegationEnabled()).isChangeable(mutable.isChangeable()).isNullable(mutable.isNullable()).rangeOfDigits(mutable.getRangeOfDigitsFromValue()).rangeOfValue(mutable.getRangeOfValue()).build();
			
			desc.setDescriptor(correctImpl);
		}
	}

	/* (non-Javadoc)
	 * @see org.kopsox.util.xml.parser.impl.AbstractKopsoxXMLParser#getPathToDTD()
	 */
	@Override
	public String getPathToDTD() {
		return "org/kopsox/environment/proxy/config/xml/valuesXMLDefinition.dtd";
	}
	
	private final Range createRange(String lower,String upper,String type) {
		
		try {
			Class<?> clazz = Class.forName(type);
			
			if(clazz.equals(BigDecimal.class)) {
				return new BigDecimalRange((BigDecimal)ValueTypeEnum.BIGDECIMAL.convert(lower),(BigDecimal)ValueTypeEnum.BIGDECIMAL.convert(upper));
			}else if(clazz.equals(java.util.Date.class)) {
				return new DateRange((java.util.Date)ValueTypeEnum.UTILDATE.convert(lower),(java.util.Date)ValueTypeEnum.UTILDATE.convert(upper));
			}else if(clazz.equals(Double.class) || clazz.equals(Float.class)) {
				return new DoubleRange((Double)ValueTypeEnum.DOUBLE.convert(lower),(Double)ValueTypeEnum.DOUBLE.convert(upper));
			}else {
				return new IntegerRange((Integer)ValueTypeEnum.INTEGER.convert(lower),(Integer)ValueTypeEnum.INTEGER.convert(upper));
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	static protected class ValueDescriptorMutableImpl implements ValueDescriptor{
		private static final long serialVersionUID = 3996260918712903228L;
		
		private String name;
		private String namespace;
		private boolean isNullable = false;
		private boolean isChangeable = false;
		private boolean isAutoValidationEnabled = false;
		private IntegerRange rangeOfDigits;
		private Range rangeOfValue;
		private boolean isHidden = false;
		private boolean isAutoErrorDelegationEnabled = true;
		
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("ValueDescriptorMutableImpl[");
			sb.append("name=");
			sb.append(name);
			sb.append(",namespace=");
			sb.append(namespace);
			sb.append(",isAutoValidationEnabled=");
			sb.append(isAutoValidationEnabled);
			sb.append(",isAutoErrorDelegationEnabled=");
			sb.append(isAutoErrorDelegationEnabled);
			sb.append(",isNullable=");
			sb.append(isNullable);
			sb.append(",isChangeable=");
			sb.append(isChangeable);
			sb.append(",rangeOfDigits=");
			sb.append(rangeOfDigits);
			sb.append(",rangeOfValue=");
			sb.append(rangeOfValue);
			sb.append(",isHidden=");
			sb.append(isHidden);
			sb.append("]");
			
			return sb.toString();
		}
		
		/* (non-Javadoc)
		 * @see org.kopsox.data.ValueDescriptor#isAutomaticErrorDelegationEnabled()
		 */
		@Override
		public boolean isAutomaticErrorDelegationEnabled() {
			return this.isAutoErrorDelegationEnabled;
		}

		/* (non-Javadoc)
		 * @see org.kopsox.data.ValueDescriptor#setAutomaticErrorDelegation(boolean)
		 */
		@Override
		public void setAutomaticErrorDelegation(boolean flag) {
			this.isAutoErrorDelegationEnabled = flag;
		}

		/* (non-Javadoc)
		 * @see org.kopsox.data.ValueDescriptor#isHidden()
		 */
		@Override
		public boolean isHidden() {
			return this.isHidden;
		}
		
		/**
		 * @param isHidden the isHidden to set
		 */
		public void setHidden(boolean isHidden) {
			this.isHidden = isHidden;
		}

		/**
		 * @return the name
		 */
		public String getName() {
			return name;
		}
		/**
		 * @param name the name to set
		 */
		public void setName(String name) {
			this.name = name;
		}
		/**
		 * @return the namespace
		 */
		public String getNamespace() {
			return namespace;
		}
		/**
		 * @param namespace the namespace to set
		 */
		public void setNamespace(String namespace) {
			this.namespace = namespace;
		}
		/**
		 * @return the isNullable
		 */
		public boolean isNullable() {
			return isNullable;
		}
		/**
		 * @param isNullable the isNullable to set
		 */
		public void setNullable(boolean isNullable) {
			this.isNullable = isNullable;
		}
		/**
		 * @return the isChangeable
		 */
		public boolean isChangeable() {
			return isChangeable;
		}
		/**
		 * @param isChangeable the isChangeable to set
		 */
		public void setChangeable(boolean isChangeable) {
			this.isChangeable = isChangeable;
		}
		/**
		 * @return the isAutoValidationEnabled
		 */
		public boolean isAutoValidationEnabled() {
			return isAutoValidationEnabled;
		}
		/**
		 * @param isAutoValidationEnabled the isAutoValidationEnabled to set
		 */
		public void setAutoValidationEnabled(boolean isAutoValidationEnabled) {
			this.isAutoValidationEnabled = isAutoValidationEnabled;
		}
		/**
		 * @return the rangeOfDigits
		 */
		public IntegerRange getRangeOfDigitsFromValue() {
			return rangeOfDigits;
		}
		/**
		 * @param rangeOfDigits the rangeOfDigits to set
		 */
		public void setRangeOfDigits(IntegerRange rangeOfDigits) {
			this.rangeOfDigits = rangeOfDigits;
		}
		/**
		 * @return the rangeOfValue
		 */
		public Range getRangeOfValue() {
			return rangeOfValue;
		}
		/**
		 * @param rangeOfValue the rangeOfValue to set
		 */
		public void setRangeOfValue(Range rangeOfValue) {
			this.rangeOfValue = rangeOfValue;
		}
	}
}
